package com.taikang.dao.base;

import org.nutz.dao.*;
import org.nutz.dao.pager.Pager;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.sql.SqlCallback;
import org.nutz.dao.util.Daos;

import com.taikang.util.page.Pagination;
import com.taikang.util.AssUtils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * Created by Alan on 16/3/7.
 */
@Component
public class DefaultCommonDaoImpl<T> extends AbstractCommonDao<T> implements CommonDao<T> {

    @Autowired
    public void setDao(Dao dao) {
        this.dao = dao;
    }

    public Dao getDao() {
        return this.dao;
    }

    @Override
    public T fetch(long id) {
        return this.dao.fetch(this.getEntityClass(), id);
    }

    @Override
    public T fetch(String name) {
        return this.dao.fetch(this.getEntityClass(), name);
    }

    @Override
    public T fetchWith(long id, String obj) {
        T t = fetch(id);
        return dao.fetchLinks(t, obj);
    }

    @Override
    public Object fetch(Class cls, long id) {
        return this.dao.fetch(cls, id);
    }

    @Override
    public Object fetch(Class cls, String name) {
        return this.dao.fetch(cls, name);
    }

    @Override
    public Object create(Object o) {
        return this.dao.insert(o);
    }

    @Override
    public int update(Object o) {
        return Daos.ext(dao, FieldFilter.create(this.getEntityClass(), true)).update(o);
    }

    @Override
    public int delete(Object o) {
        return this.dao.delete(o);
    }


    @Override
    public Pagination listPage(Integer pageNumber, Condition cnd) {
        return listPage(pageNumber, DEFAULT_PAGE_NUMBER, cnd);
    }

    @Override
    public Pagination listPage(Integer pageNumber, Sql sql) {
        return listPage(pageNumber, DEFAULT_PAGE_NUMBER, sql);
    }

    @Override
    public Pagination listPage(Integer pageNumber, int pageSize, Condition cnd) {
        pageNumber = getPageNumber(pageNumber);
        pageSize = getPageSize(pageSize);
        Pager pager = this.dao.createPager(pageNumber, pageSize);
        List<T> list = this.getDao().query(getEntityClass(), cnd, pager);
        pager.setRecordCount(this.dao.count(getEntityClass(), cnd));
        return new Pagination(pageNumber, pageSize, pager.getRecordCount(), list);
    }

    public Pagination listPage(Integer pageNumber, int pageSize, Condition cnd, Class cls) {
        pageNumber = getPageNumber(pageNumber);
        pageSize = getPageSize(pageSize);
        Pager pager = this.dao.createPager(pageNumber, pageSize);
        List<T> list = this.getDao().query(cls, cnd, pager);
        pager.setRecordCount(this.dao.count(cls, cnd));
        return new Pagination(pageNumber, pageSize, pager.getRecordCount(), list);
    }

    @Override
    public Pagination listPage(Integer pageNumber, int pageSize, Sql sql) {
        pageNumber = getPageNumber(pageNumber);
        pageSize = getPageSize(pageSize);
        Pager pager = this.dao.createPager(pageNumber, pageSize);
        pager.setRecordCount((int) Daos.queryCount(dao, sql.toString()));// 记录数需手动设置
        sql.setPager(pager);
        dao.execute(sql);
        return new Pagination(pageNumber, pageSize, pager.getRecordCount(), sql.getList(getEntityClass()));
    }

    @Override
    public T queryObject(String sqlStr, Map<String, Object> varMap, Map<String, Object> paramsMap, Class cls) {
        Sql sql = Sqls.create(sqlStr);
        sql.setCallback(Sqls.callback.entities());
        sql.setEntity(this.dao.getEntity(cls));
        if (varMap != null && varMap.size() > 0) {
            for (Map.Entry<String, Object> entry : varMap.entrySet()) {
                sql.setVar(entry.getKey(), entry.getValue());
            }
        }
        if (paramsMap != null && paramsMap.size() > 0) {
            for (Map.Entry<String, Object> entry : paramsMap.entrySet()) {
                sql.setParam(entry.getKey(), entry.getValue());
            }
        }
        this.dao.execute(sql);
        return (T) sql.getObject(cls);
    }

    @Override
    public List<T> query(Condition cnd) {
        return this.dao.query(this.getEntityClass(), cnd);
    }

    @Override
    public List<T> query(String sqlStr, Class cls) {
        return query(sqlStr, null, null, cls);
    }

    @Override
    public Integer execute(String sqlStr, Map<String, Object> varMap, Map<String, Object> paramsMap) {
        Sql sql = Sqls.create(sqlStr);
        if (varMap != null && varMap.size() > 0) {
            for (Map.Entry<String, Object> entry : varMap.entrySet()) {
                sql.setVar(entry.getKey(), entry.getValue());
            }
        }
        if (paramsMap != null && paramsMap.size() > 0) {
            for (Map.Entry<String, Object> entry : paramsMap.entrySet()) {
                sql.setParam(entry.getKey(), entry.getValue());
            }
        }
        return this.dao.execute(sql).getUpdateCount();
    }

    @Override
    public List<T> query(String sqlStr, Map<String, Object> varMap, Map<String, Object> paramsMap) {
        return query(sqlStr, varMap, paramsMap, this.getEntityClass());
    }

    @Override
    public List query(String sqlStr, Map<String, Object> varMap, Map<String, Object> paramsMap, Class cls) {
        Sql sql = Sqls.create(sqlStr);
        sql.setCallback(Sqls.callback.entities());
        sql.setEntity(this.dao.getEntity(cls));
        if (varMap != null && varMap.size() > 0) {
            for (Map.Entry<String, Object> entry : varMap.entrySet()) {
                sql.setVar(entry.getKey(), entry.getValue());
            }
        }
        if (paramsMap != null && paramsMap.size() > 0) {
            for (Map.Entry<String, Object> entry : paramsMap.entrySet()) {
                sql.setParam(entry.getKey(), entry.getValue());
            }
        }
        this.dao.execute(sql);
        return sql.getList(cls);
    }

    public T queryForOne(Condition cnd) {
        List<T> list = query(cnd);
        if (list.size() == 1)
            return list.get(0);
        return null;
    }


    public Object queryForOne(Condition cnd, Class cls) {
        List<T> list = getDao().query(cls, cnd);
        if (list.size() == 1)
            return list.get(0);
        return null;
    }

    @Override
    public Object execute(Sql sql) {
        return dao.execute(sql);
    }

    @Override
    public Integer createWithLinks(Object o, String regex) {
        try {
            Object result = dao.insertWith(o, regex);
            if (null == result) {
                return 0;
            } else {
                if (result instanceof List) {
                    return ((List) result).size();
                }
            }
            return 1;
        } catch (Exception e) {
            logger.error(AssUtils.parse(e));
            return 0;
        }
    }

    @Override
    public Integer updateWithLinks(Object o, String regex) {
        try {
            List<T> result = (List<T>) Daos.ext(dao, FieldFilter.create(o.getClass(), true)).updateWith(o, regex);
            return result.size();
        } catch (Exception e) {
            logger.error(AssUtils.parse(e));
            return 0;
        }
    }

    public T queryForOne(Cnd cnd) {
        List<T> list = query(cnd);
        if (list.size() == 1) {
            return list.get(0);
        }
        return null;
    }

    @Override
    public SqlCallback createSqlCallBack(final boolean listOrNot, final Class cls) {
        return new SqlCallback() {
            @Override
            public Object invoke(Connection conn, ResultSet rs, Sql sql) throws SQLException {
                Object result = null;
                ResultSetMetaData data = rs.getMetaData();
                try {
                    result = listOrNot ? new ArrayList<T>() : cls.newInstance();
                    while (rs.next()) {
                        T t = (T) cls.newInstance();
                        for (int i = 0; i < data.getColumnCount(); i++) {
                            String setterMethodName = "set" + data.getColumnLabel(i + 1);
                            Object[] params = getTypeAndValue(data.getColumnTypeName(i + 1), rs.getObject(i + 1));
                            Method method = cls.getMethod(setterMethodName, (Class<?>) params[0]);
                            method.invoke(t, params[1]);
                        }
                        if (listOrNot) {
                            ((ArrayList<T>) result).add(t);
                        } else {
                            return t;
                        }
                    }
                } catch (Exception e) {
                    logger.error(AssUtils.parse(e));
                }
                return result;
            }
        };
    }

    private Object[] getTypeAndValue(String typeName, Object obj) throws Exception {
        typeName = typeName.toLowerCase();
        if (typeName.contains("int") || typeName.contains("long") || typeName.contains("short") || typeName.contains("byte")) {
            Integer value;
            if (typeName.contains("long") || typeName.contains("unsigned")) {
                value = null == obj ? null : new Integer(((Long) obj).intValue());
            } else if (typeName.contains("tiny")) {
                boolean tmpValue = null != obj ? (Boolean) obj : false;
                if (tmpValue) {
                    value = 1;
                } else {
                    value = 0;
                }
            } else {
                value = (Integer) obj;
            }
            return new Object[]{Integer.class, value};
        } else if (typeName.contains("char")) {
            String value = null != obj ? (String) obj : null;
            return new Object[]{String.class, value};
        } else if (typeName.contains("date") || typeName.contains("timestamp")) {
            Date value = null != obj ? (Date) obj : null;
            return new Object[]{Date.class, value};
        } else if (typeName.contains("decimal") || typeName.contains("double")) {
            BigDecimal value = null != obj ? (BigDecimal) obj : null;
            return new Object[]{Double.class, null != value ? value.doubleValue() : null};
        } else if (typeName.contains("blob")) {
            byte[] value = null != obj ? (byte[]) obj : null;
            return new Object[]{String.class, null != value ? new String(value) : null};
        } else {
            return null;
        }
    }
}
